﻿using System;
using System.Collections.Generic;
#if DEBUG
using System.Diagnostics;
#endif // #if DEBUG
using System.Linq;
using System.Text;

using FRL.DebtChased.Extensions;
using FRL.DebtChased.WebServices;

namespace FRL.DebtChased.Types
{

	/// <summary>
	/// Declaration of Money class
	/// </summary>
	public partial class Money
    {

        /////////////////////////////////////////////////////////////
        // Enumerations

        public enum eCurrency
        {
            NotSet = 0,
            Mixed,
            Sterling,
            USDollar,
            Euro,
            Yen
        }

        /////////////////////////////////////////////////////////////
        // Constants

        private const char GBPound = '£';
        private const char USDollar = '$';
        private const char Euro = '€';
        private const char Yen = '¥';

        public const string SterlingCode = "GBP";
        public const string USDollarCode = "USD";
        public const string EuroCode = "EUR";
        public const string YenCode = "JPY";
        public const string DefaultCurrency = SterlingCode;

        /////////////////////////////////////////////////////////////
        // Attributes

        private decimal _amount;
        public decimal Amount
        {
            get { return _amount; }
            set { _amount = value; }
        }

        private eCurrency _currency;
        public eCurrency Currency
        {
            get { return _currency; }
        }
        private eCurrency SetCurrency
        {
            set
            {
                _currency = value;
                _currencyCode = CurrencyToCode(_currency);
            }
        }

        private string _currencyCode;
        public string CurrencyCode
        {
            get { return _currencyCode; }
            set
            {
                if (string.IsNullOrWhiteSpace(value))
                {
                    _currency = eCurrency.Sterling;
                    _currencyCode = SterlingCode;
                }
                else
                {
                    _currencyCode = value.ToUpper().Trim();
                    if (_currencyCode == GBPound.ToString()) _currencyCode = SterlingCode;
                    else if (_currencyCode == USDollar.ToString()) _currencyCode = USDollarCode;
                    else if (_currencyCode == Euro.ToString()) _currencyCode = EuroCode;
                    else if (_currencyCode == Yen.ToString()) _currencyCode = YenCode;

                    if (_currencyCode == SterlingCode) _currency = eCurrency.Sterling;
                    else if (_currencyCode == USDollarCode) _currency = eCurrency.USDollar;
                    else if (_currencyCode == EuroCode) _currency = eCurrency.Euro;
                    else if (_currencyCode == YenCode) _currency = eCurrency.Yen;
                    else _currency = eCurrency.Mixed;
                }
            }
        }

        public string CurrencySymbol
        {
            get
            {
                if (CurrencyCode == SterlingCode) return GBPound.ToString();
                if (CurrencyCode == USDollarCode) return USDollar.ToString();
                if (CurrencyCode == EuroCode) return Euro.ToString();
                if (CurrencyCode == YenCode) return Yen.ToString();
                return CurrencyCode;
            }
        }

        /////////////////////////////////////////////////////////////
        // Construction

        public Money(Money source)
        {
            this._amount = source._amount;
            this._currency = source._currency;
            this._currencyCode = source.CurrencyCode;
        }

        public Money(decimal amount, eCurrency currency)
        {
            this._amount = amount;
            this.SetCurrency = currency;
        }

        public Money(decimal amount, string currencyCode)
        {
            this._amount = amount;
            this.CurrencyCode = currencyCode;
            this._currency = ParseCurrencyCode(currencyCode);
        }

        public Money(CurrencyTransport ct)
        {
            this._amount = ct.Amount;
            this.CurrencyCode = ct.CurrencyCode;
            this._currency = ParseCurrencyCode(ct.CurrencyCode);
        }

        public Money(decimal amount)
        {
            this.Amount = amount;
            this.CurrencyCode = SterlingCode;
        }

        /////////////////////////////////////////////////////////////////
        #region Implementation

        public override string ToString()
        {
            return Format(true);
        }

        public override bool Equals(object obj)
        {
            Money mn = (Money)obj;
            if (object.Equals(mn, null))
                return false;
            if (this.Currency > eCurrency.Mixed)
                return ((mn.Currency == this.Currency) && (mn.Amount == this.Amount));
            else
                return ((mn.CurrencyCode == this.CurrencyCode) && (mn.Amount == this.Amount));
        }

        public override int GetHashCode()
        {
            return this.ToString().GetHashCode();
        }

        public static bool operator ==(Money mn1, Money mn2)
        {
            if (object.Equals(mn1, null))
                return object.Equals(mn2, null);
            else
                return mn1.Equals(mn2);
        }

        public static bool operator !=(Money mn1, Money mn2)
        {
            if (object.Equals(mn1, null))
                return (!object.Equals(mn2, null));
            else
                return !mn1.Equals(mn2);
        }

        public static Money operator +(Money mn1, Money mn2)
        {

            // shortcuts
            if ((mn1.Currency == eCurrency.Mixed) || (mn2.Currency == eCurrency.Mixed))
                return new Money(0, eCurrency.Mixed);
            if ((mn1.Currency == eCurrency.NotSet) || (mn2.Currency == eCurrency.NotSet))
                return new Money(0, eCurrency.Mixed);

            // If currencies do not match...
            if (mn1.Currency != mn2.Currency)
                return new Money(0, eCurrency.Mixed);
            else
                return new Money(mn1.Amount + mn2.Amount, mn1.Currency);

        }

        public static Money operator -(Money mn1, Money mn2)
        {

            // shortcuts
            if ((mn1.Currency == eCurrency.Mixed) || (mn2.Currency == eCurrency.Mixed))
                return new Money(0, eCurrency.Mixed);
            if ((mn1.Currency == eCurrency.NotSet) || (mn2.Currency == eCurrency.NotSet))
            {
                return new Money(0, eCurrency.Mixed);
            }

            // If currencies do not match...
            if (mn1.Currency != mn2.Currency)
                return new Money(0, eCurrency.Mixed);
            else
                return new Money(mn1.Amount - mn2.Amount, mn1.Currency);

        }

        public static implicit operator Money(CurrencyTransport ct)
        {
            if (ct == null) return null;
            return new Money(ct);
        }
        public static explicit operator CurrencyTransport(Money m)
        {
            if (m == null) return null;
            CurrencyTransport ct = new CurrencyTransport();
            ct.CurrencyCode = m.CurrencyCode;
            ct.Amount = m.Amount;
            return ct;
        }

        public static explicit operator Money(string s)
        {
            return ToMoney(s);
        }

        #endregion Implementation

        /////////////////////////////////////////////////////////////
        // Operations

        public string Format(bool bWithBrackets)
        {
            // shortcuts
            if (this.Currency == eCurrency.Mixed)
                return "[MIXED]";
            if (this.Currency == eCurrency.NotSet)
                return "[UNKNOWN]";

            // Format a string
            StringBuilder sb = new StringBuilder();

            // If formating negatives with brackets...
            if (bWithBrackets)
            {

                // Use brackets for negative values, example, $(123.45)

                // Prefix with currency sign
                sb.Append(CurrencySymbol + " ");

                // Format decimal
                if (this._amount < 0)
                    sb.AppendFormat("({0:#,0.00})", Math.Abs(this._amount));
                else
                    sb.AppendFormat("{0:#,0.00}", this._amount);

            }
            else
            {

                // Use minus sign, example, -$123.45

                // Add sign
                if (this._amount < 0)
                    sb.Append("-");

                // Add currency
                sb.Append(CurrencySymbol + " ");

                // Add value
                sb.Append(Math.Abs(this.Amount).ToString("#,0.00"));

            }

            // Return string
            return sb.ToString();

        }

        /////////////////////////////////////////////////////////////
        // Static Operations

        public static Money ToMoney(string value)
        {
            return ToMoney(value, string.Empty);
        }
        public static Money ToMoney(string value, eCurrency defaultCurrency)
        {
            return ToMoney(value, CurrencyToCode(defaultCurrency));
        }
        public static Money ToMoney(string value, string defaultCurrency)
        {
            if (string.IsNullOrWhiteSpace(value)) return null;

            bool minus = false;
            decimal amount = 0;
            string code = null;
            string work = value.Trim();

            if (work.Contains("-"))
            {
                work = work.Remove(work.LastIndexOf("-"), 1).Trim();
                minus = true;
            }
            else if (work.Contains("(") && work.Contains(")"))
            {
                work = work.Remove(work.LastIndexOf("("), 1);
                work = work.Remove(work.LastIndexOf(")"), 1).Trim();
                minus = true;
            }

            int i = 0;
            while (i < work.Length && !".0123456789".Contains(work[i])) i++;
            if (i > 0)
            {
                code = work.Substring(0, i).Trim().ToUpper();
                work = work.Remove(0, i);
            }

            if (string.IsNullOrWhiteSpace(code)) code = (!string.IsNullOrWhiteSpace(defaultCurrency)) ? defaultCurrency : DefaultCurrency;
            if (!IsValidCurrencyCode(code))
            {
                throw new Exception(string.Format("Invalid monetary value {0}.", value));
            }

            if (!string.IsNullOrWhiteSpace(work))
            {
                try { amount = Math.Round((Convert.ToDecimal(work)), 2); }
                catch
                {
                    throw new Exception(string.Format("Invalid monetary value {0}.", value));
                }
                if (minus && amount > 0) amount = -amount;
            }

            Money money = new Money(amount, code);
            return money;
        }

        public static bool IsValidCurrencyCode(string code)
        {
            if (code == GBPound.ToString() || code == USDollar.ToString() || code == Euro.ToString() || code == Yen.ToString())
                return true;

            //TODO replace this code with a database currency table validation check;
            if (code.Length == 3) return true;
            return false;
        }

        public static Money.eCurrency ParseCurrencyCode(string strCode)
        {

            // declare return variable
            eCurrency Ftmp = eCurrency.NotSet;

            // Compare currencies
#if SILVERLIGHT
            if ((string.Compare(strCode, GBPound.ToString(), StringComparison.CurrentCulture) == 0) || (string.Compare(strCode, SterlingCode, StringComparison.CurrentCulture) == 0))
                Ftmp = eCurrency.Sterling;
            else if ((string.Compare(strCode, USDollar.ToString(), StringComparison.CurrentCulture) == 0) || (string.Compare(strCode, USDollarCode, StringComparison.CurrentCulture) == 0))
                Ftmp = eCurrency.USDollar;
            else if ((string.Compare(strCode, Euro.ToString(), StringComparison.CurrentCulture) == 0) || (string.Compare(strCode, EuroCode, StringComparison.CurrentCulture) == 0))
                Ftmp = eCurrency.Euro;
            else if ((string.Compare(strCode, Yen.ToString(), StringComparison.CurrentCulture) == 0) || (string.Compare(strCode, YenCode, StringComparison.CurrentCulture) == 0))
                Ftmp = eCurrency.Yen;
            else if (!string.IsNullOrWhiteSpace(strCode))
                Ftmp = eCurrency.Mixed;
#else
            if ((string.Compare(strCode, GBPound.ToString(), true) == 0) || (string.Compare(strCode, SterlingCode, true) == 0))
                Ftmp = eCurrency.Sterling;
            else if ((string.Compare(strCode, USDollar.ToString(), true) == 0) || (string.Compare(strCode, USDollarCode, true) == 0))
                Ftmp = eCurrency.USDollar;
            else if ((string.Compare(strCode, Euro.ToString(), true) == 0) || (string.Compare(strCode, EuroCode, true) == 0))
                Ftmp = eCurrency.Euro;
            else if ((string.Compare(strCode, Yen.ToString(), true) == 0) || (string.Compare(strCode, YenCode, true) == 0))
                Ftmp = eCurrency.Yen;
#endif // #if SILVERLIGHT

            // return variable
            return Ftmp;

        }

        public static string CurrencyToCode(eCurrency Currency)
        {

            // declare return variable
            string strFtmp = string.Empty;

            // Compare currencies
            switch (Currency)
            {
                case eCurrency.Sterling: strFtmp = "GBP"; break;
                case eCurrency.USDollar: strFtmp = "USD"; break;
                case eCurrency.Euro: strFtmp = "EUR"; break;
                case eCurrency.Yen: strFtmp = "JPY"; break;
            } // switch

            // return variable
            return strFtmp;

        }
        public static string CurrencyToSymbol(eCurrency Currency)
        {

            // Compare codes
            if (Currency == eCurrency.Sterling)
                return GBPound.ToString();
            else if (Currency == eCurrency.USDollar)
                return USDollar.ToString();
            else if (Currency == eCurrency.Euro)
                return Euro.ToString();
            else if (Currency == eCurrency.Yen)
                return Yen.ToString();
            else
            {
                return string.Empty;
            }

        }

        public static string SuperSmartAddition(List<Money> moneyList, bool bWithBrackets)
        {
#if DEBUG
            Debug.Assert(moneyList.Count > 0);
#endif // #if DEBUG

            // Declare return variable
            string strFtmp;

            // Declaration of local variables
            decimal dcTotal = 0M;

            // Check for homogenous currency
            bool bHomogenousCurrency = true;
            dcTotal = 0.00M;
            for (int i1 = 0; i1 < moneyList.Count; i1++)
            {
                if (moneyList[i1].Currency == moneyList[0].Currency)
                    dcTotal += moneyList[i1].Amount;
                else
                {
                    bHomogenousCurrency = false;
                    break;
                }
            }

            // If homogenous currency...
            if (bHomogenousCurrency)
            {

                // Format currency
                strFtmp = new Money(dcTotal, moneyList[0].Currency).Format(bWithBrackets);

            }
            else
            {

                // Get list of currencies
                eCurrency[] currencyList = new eCurrency[4] {
					eCurrency.Sterling,
					eCurrency.USDollar,
					eCurrency.Euro,
					eCurrency.Yen
				};

                // Add up by grouping currencies
                List<string> strTotals = new List<string>();
                foreach (eCurrency c in currencyList)
                {

                    // Calculate total for this currency
                    dcTotal = 0.00M;
                    foreach (Money mn in moneyList)
                    {
                        if (mn.Currency == c)
                            dcTotal += mn.Amount;
                    }

                    // If there is something to add...
                    if (dcTotal != 0)
                        strTotals.Add(new Money(dcTotal, c).Format(bWithBrackets));

                } // forloop

                // Build a string from the list
                strFtmp = ToAndList(strTotals);

            }

            // Return variable
            return strFtmp;

        }

        private static string ToAndList(List<string> list)
        {

            // Shortcuts
            if (list.Count == 0)
                return string.Empty;
            else if (list.Count == 1)
                return list[0];

            // Build a string
            StringBuilder sb = new StringBuilder();
            for (int i1 = 0; i1 < list.Count; i1++)
            {
                if ((i1 > 0) && (i1 < (list.Count - 1)))
                    sb.Append(", ");
                else if ((i1 == (list.Count - 1)))
                    sb.Append(" and ");
                sb.Append(list[i1]);
            }

            // Return string
            return sb.ToString();

        }

    }

}
